package org.nerve.dir.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import org.nerve.dir.Content;
import org.nerve.dir.ContentType;
import org.nerve.dir.IParser;
import org.nerve.dir.exception.ParserException;
import org.nerve.utils.DateUtils;
import org.nerve.utils.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Pattern;

/**
 * 针对window系统dir命令结果的解析器
 * com.nerve.jiepu.forensicsystem.parser.file
 * Created by zengxm on 2015/7/15 0015.
 */
public class WindowDirParser implements IParser {

	public enum Flag {
		DIR      //下一行为dir描述行，应该调用dealWithDirLine
		,NEXT_LOOP  //下一行开始是另外一个循环
		,LOOP   //循环读取File 或者 Directory
	}

	/**
	 #---------------------------------------------------------------------------
	 #对于dir导出的数据中包含了目录或者文件的最后修改日期，格式因系统而异
	 #常用格式有
	 #2015/07/07 周二  下午 05:44
	 #2015/04/02  10:19
	 #2015/04/02 Mon 10:19
	 #2015/04/02 Mon AM 10:19
	 #
	 #考虑到兼容性（中文、英文以及其他语种），在解析这段时间字符串时，程序会将星期几的描述字符删除（没有什么卵用，只要有年月日都可以解析出日期）
	 #然后再将PM 、 AM 相关的描述字符统一改成AM PM（如上午 会被替换成 AM，下午 被替换成 PM，有其他语种的也是同样道理）
	 #最后用 yyyy/MM/dd a hh:mm 这样的格式去解析出最后的日期对象
	 #
	 #注意，regex标识了这个是使用正则表达式
	 #---------------------------------------------------------------------------
	 详细请见 win-dir.properties
	 */
	private String weekRegex;
	private String amRegex;
	private String pmRegex;
	private String directoryRegexFirst;
	private String directoryRegexEnd;
	private String invalidNameRegex="^[\\.]{1,2}$";     //无效名称的判断
	private String[] datetimePattern;
	private boolean partition;  //处理目录行时，是否只处理分区盘

	/**
	 * dir命令行导出的数据中，标识当前目录的格式类似于
	 *
	    D:\ 的目录

	 * 即上下一个空行中间的描述就是当前的路径，这里我们要将此路径取出来
	 * 当前目录
	 */
	private String currentPath;

	private Flag flag = Flag.NEXT_LOOP;

	public WindowDirParser() throws IOException {
		super();
		init(getClass().getResourceAsStream("/win-dir.properties"));
	}

	public WindowDirParser(final InputStream propertiesIS) throws IOException {
		super();

		this.init(propertiesIS);
	}

	public void init(final InputStream propertiesIS) throws IOException {
		Properties properties = new Properties();
		properties.load(propertiesIS);
		weekRegex = properties.getProperty("regex.week", "");
		amRegex = properties.getProperty("regex.am", "");
		pmRegex = properties.getProperty("regex.pm", "");
		directoryRegexFirst = properties.getProperty("regex.directory.first", "");
		directoryRegexEnd = properties.getProperty("regex.directory.end", "");

		try {
			String encoding = properties.getProperty("encoding", "utf-8");
			weekRegex = new String(weekRegex.getBytes("ISO8859-1"), encoding);
			amRegex = new String(amRegex.getBytes("ISO8859-1"), encoding);
			pmRegex = new String(pmRegex.getBytes("ISO8859-1"), encoding);
			directoryRegexFirst = new String(directoryRegexFirst.getBytes("ISO8859-1"), encoding);
			directoryRegexEnd = new String(directoryRegexEnd.getBytes("ISO8859-1"), encoding);

			partition = Boolean.valueOf(properties.getProperty("partition", "true"));
		} catch (Exception e) {
			e.printStackTrace();
		}

		//时间格式
		JSONArray array = JSON.parseArray(
				properties.getProperty(
						"regex.datetime",
						"['yyyy/MM/dd a hh:mm','MM/dd/yyyy hh:mm a']"));
		datetimePattern = new String[array.size()];
		array.toArray(datetimePattern);
	}

	public String[] getDatetimePattern() {
		return datetimePattern;
	}
	public WindowDirParser setDatetimePattern(String[] datetimePattern) {
		this.datetimePattern = datetimePattern;
		return this;
	}

	/**
	 * 从一行数据中解析出content对象
	 *
	 * 一行格式如下（window 8.1 中文版）：
	 * 2015/05/14 周四  下午 03:47     3,312,424,533 android-sdk-windows.rar
	 *
	 * 解析流程如下：
	 * 1.在第一次出现三个空格的地方分成两部分，前部分就是日期+时间，后部分就是文件类型+文件名
	 * 2.时间日期部分使用@parseDate方法解析
	 * 3.后半部分又以第一个空格分成两部分AB
	 * 4.A部分如果不存在DIR字符，就表明是文件，然后A对应了该文件的大小（如上面的样例）
	 * 5.B部分就是文件名称
	 *
	 * @param line
	 * @return
	 * @throws Exception
	 */
	@Override
	public Content parse(String line) throws Exception {
		if(line == null){
			return null;
		}

		//判断是否空行
		if(line.trim().length()==0){
			dealWithEmptyLine();
//			System.out.println("dealWithEmptyLine="+flag);
			return null;
		}
		//如果dirFlag=true，则要处理父目录
		else if(flag== Flag.DIR){
//			System.out.println("dealWithDirLine"+line);
			return dealWithDirLine(line);
		}
		//如果不是以数字开头，忽略
		else if((int)line.charAt(0)<48 || (int)line.charAt(0)>57){
			flag = Flag.NEXT_LOOP;
			return null;
		}
		else
			return parseDo(line);
	}

	private Content parseDo(String line){
		Content c=new Content();

		//步骤1
		String temp[]=line.split("   ");
		if(temp.length<2)
			throw new ParserException("解析DIR 命令行结果出错：步骤1无法分割字符串"+line);

		//步骤2
		Date modifyDate=parseDate(temp[0]);
		if(modifyDate !=null)
			c.setLastWriteTime(modifyDate.getTime());

		//步骤3
		String typeAndNameStr = arrayToString(temp,1);
		String[] temp2 = typeAndNameStr.split(" ");

		if(temp2.length<2)
			throw new ParserException("解析DIR 命令行结果出错：步骤4无法分割字符串"+temp[1]);
		//步骤4
		//包含了DIR就是文件夹
		if(StringUtils.contains(temp2[0],"DIR")){
			c.setContentType(ContentType.DIRECTORY);
		}else{
			c.setContentType(ContentType.FILE);
			//解析文件大小
			c.setSize(parseSize(temp2[0]));
		}

		//取文件名
		c.setName(typeAndNameStr.substring(
				typeAndNameStr.indexOf(temp2[1])
		));

		/*
		有时候会出现 ., .. 这样没有意义的文件名，这里需要清空，样例如下：
		 C:\data\Common 的目录

		2015/07/17  09:51    <DIR>          .
		2015/07/17  09:51    <DIR>          ..
		 */
		if(Pattern.compile(invalidNameRegex).matcher(c.getName()).find()){
			return null;
			//throw new ParserException("invalid name! regex="+invalidNameRegex);
		}
		c.setPath(currentPath);
//		System.out.println(JSON.toJSONString(c));
		return c;
	}

	/**
	 * 处理空行
	 */
	private void dealWithEmptyLine(){
		//NEXT_LOOP 时，那么下一行就是dir描述行
		if(flag == Flag.NEXT_LOOP){
			flag = Flag.DIR;
		}else if(flag== Flag.DIR){
			flag = Flag.LOOP;
		}else if(flag== Flag.LOOP){
			flag = Flag.NEXT_LOOP;
		}
	}

	/**
	 * 处理父目录行
	 * 样例数据如下：
	 *  C:\data 的目录
	 *  C:\ 的目录
	 *  Directory of C:\Windows\System32\com\dmp
	 * @param line
	 * @return
	 */
	private Content dealWithDirLine(String line){
		//先清空干扰的文字，得到完整的路径
		line = line.trim();
		int len=line.length();
		line = line.replaceFirst(directoryRegexFirst, "");
		//如果没有变化，就执行后面的匹配
		if(line.length() == len){
			line=line.replaceFirst(directoryRegexEnd, "");
		}
		//如果还是没有变化，则任务不是合法的文件夹
		if(line.length() == len){
			return null;
		}
		currentPath = line.trim();
		if(currentPath == null){
			currentPath = "";           //保证下一个空行时能正常执行下去
			throw new ParserException("父目录行没有数据");
		}

		/*
		是否只处理 顶级分区
		 */
		if(partition){
			//判断依据：文件绝对路径超过3 或者 去除 \ 之类的符号后长度不超过2
			if(currentPath.length() >3 || currentPath.replaceAll("\\\\","").length()>2)
				return null;
		}

		Content c = new Content();
		File f=new File(currentPath);
		c.setName(f.getName());
		c.setPath(f.getParent());

		if(null == f.getParent()){
			c.setName(currentPath);
		}
		c.setContentType(ContentType.DIRECTORY);

		return c;
	}

	private String arrayToString(String[] as, int from){
		StringBuilder sb=new StringBuilder();
		for(int i=from;i<as.length;i++)
			sb.append(as[i]);
		return sb.toString().trim();
	}

	private long parseSize(String str){
		if(null==str)   return 0;
		return Long.valueOf(str.trim().replaceAll(",",""));
	}

	/**
	 * 尝试从window 中的命令行得到对应的时间
	 * @param str
	 */
	private Date parseDate(String str){
		if(str==null)
			return null;
		//删除全部的星期相关字符
		str = str.replaceAll(weekRegex,"")
				.replaceAll(amRegex,"AM")
				.replaceAll(pmRegex,"PM")
		;
		str = str.trim().replaceAll("[' ']+"," ");
		Date date= DateUtils.parseDate(str);
		//使用较为常用的日期格式无法得到日期时，尝试使用yyyy/MM/dd aa hh:mm这样的格式
		if(date==null){
			try{
				date = DateUtils.parseDate(str, Locale.US, datetimePattern);
			}catch (Exception e){
				e.printStackTrace();
			}
		}
		return date;
	}

	@Override
	public String getName() {
		return "针对window系统dir命令结果的解析器";
	}

	/**
	 * 让文件读取类自己判断文件编码
	 * @return
	 */
	@Override
	public String getEncoding() {
		return null;
	}
}
